/**
 * @file: sd365-common-common-g05 com.sd365.common.core.common.exception
 * @author: Sukaiting
 * @date: 2020/8/26 16:20
 * @copyright: 2020-2023 www.bosssoft.com.cn Inc. All rights reserved.
 */

package com.bosssoft.common.exception;
import com.bosssoft.common.context.BaseContextConstants;
import com.bosssoft.common.context.BaseContextHolder;
import com.bosssoft.common.exception.code.CommonErrorCode;
import com.bosssoft.common.exception.parse.DataBaseExceptionParser;
import com.bosssoft.common.response.CommonResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.validation.ValidationException;
import java.sql.SQLException;
import java.util.IllformedLocaleException;

/**
 * @author Sukaiting
 * @version 1.1.1
 * @class GlobalControllerResolver
 * @classdesc 全局异常处理器
 * <p>维护历史记录 : 按照新的异常体系重构处理，过期了部分旧框架支持的异常类型</p>
 * <p> abel.zhan 增加了 SQLException处理，如果是数据库异常则直接展示数据库异常码 </p>
 * <p>abel.zhan 增加了 SQLException处理,SystemException,ThirdPartApiException，如果是数据库异常则直接展示数据库异常码 </p>
 * <p>abel.zhan 重构异常代码去除冗余的异常处理方法</p>
 */
@Slf4j
@RestControllerAdvice
public class GlobalControllerExceptionResolver {
    private GlobalControllerExceptionResolver() {
        // no-args constructor
    }
    /**
     * BusinessException.class 为业务异常 按照新规划的框架，违法校验规则 ，验证不通过，ACID无保证，
     * 违法业务约定都属于业务异常
     * @param exception service层业务异常都以 BusinessException抛出
     * @return 统一应答，前端取异常码显示
     */
    @ExceptionHandler(BusinessException.class)
    public CommonResponse<Object> businessException(BusinessException exception) {
        log.error("业务异常：{},异常工号:{},错误码：{} 全局业务流水号：{}, 堆栈：",
                ExceptionUtil.getAllExceptionMsg(exception),BaseContextHolder.getUserCode(),exception.getCode(),
                BaseContextHolder.get(BaseContextConstants.GLOBAL_SERIAL_NUMBER),
                exception);
        return CommonResponse.builder()
                .withCode(exception.getCode())
                .withMessage(exception.getMessage())
                .build();
    }

    /**
     *  如果是第三方api接口调用错误抛出此类异常
     * @param exception 第三方异常
     * @return 返回异常应答包前端的http 状态是显示200
     */
    @ExceptionHandler(ThirdPartyApiException.class)
    public CommonResponse<Object> thirdPartyApiException(ThirdPartyApiException exception) {
        log.error("调用第3方api异常：{},异常工号:{},错误码:{}, 全局业务流水号：{}, 堆栈：{}",
                ExceptionUtil.getAllExceptionMsg(exception),BaseContextHolder.getUserCode(),exception.getCode(),
                BaseContextHolder.get(BaseContextConstants.GLOBAL_SERIAL_NUMBER),
                exception);
        return CommonResponse.builder()
                .withCode(exception.getCode())
                .withMessage(exception.getMessage())
                .build();
    }

    /**
     *   将 内存，io 等偏向系统底层的错误统一定义为系统异常
     * @param exception 描述偏向系统底层的异常
     * @return 统一异常应答码
     */
    @ExceptionHandler(SystemException.class)
    public CommonResponse<Object> systemException(SystemException exception) {
        log.error("OS异常包括内存不足 IO 错误 类型转换错误 空指针等：{},异常工号:{},错误码:{}, 全局业务流水号：{}, 堆栈：{}",
                ExceptionUtil.getAllExceptionMsg(exception),BaseContextHolder.getUserCode(),exception.getCode(),
                BaseContextHolder.get(BaseContextConstants.GLOBAL_SERIAL_NUMBER),
                exception);
        return CommonResponse.builder()
                .withCode(exception.getCode())
                .withMessage(exception.getMessage())
                .build();
    }


    /**
     *  1.0.1-RELEASE版本之后将daoException 统一到 DataBaseException
     * @param exception
     * @return
     */

    /**
     * 参数绑定异常
     * @param exception
     * @return 只要绑定错误都统一归次异常处理
     */
    @ExceptionHandler(BindException.class)
    public CommonResponse<Object> handleBindException(BindException exception) {
        //校验 除了 requestbody 注解方式的参数校验 对应的 bindingresult 为 BeanPropertyBindingResult
        FieldError fieldError = exception.getBindingResult().getFieldError();
        log.error("必填校验异常:{}({}),异常工号:{},全局业务流水号：{}", fieldError.getDefaultMessage(), BaseContextHolder.getUserCode(),fieldError.getField(),BaseContextHolder.get(BaseContextConstants.GLOBAL_SERIAL_NUMBER));
        return CommonResponse.builder()
                .withCode(CommonErrorCode.SYSEM_PARAM_ERROR.getCode())// 依据设计文档定义B001 代表参数校验错误
                .withMessage(exception.getMessage())
                .build();
    }

    /**
     * 非法参数异常
     * @param exception 非法参数
     * @return 统一应答
     */
    @ExceptionHandler(IllformedLocaleException.class)
    public CommonResponse<Object> IllformedLocaleException(IllformedLocaleException exception) {
        log.error("非法参数异常:{}({}),异常工号:{},全局业务流水号：{}", exception.getMessage(), BaseContextHolder.getUserCode(),
                ExceptionUtil.getAllExceptionMsg(exception),BaseContextHolder.get(BaseContextConstants.GLOBAL_SERIAL_NUMBER));
        return CommonResponse.builder()
                .withCode(CommonErrorCode.SYSEM_PARAM_ERROR.getCode())// 依据设计文档定义B001 代表参数校验错误
                .withMessage(exception.getMessage())
                .build();
    }

    /**
     * 类型转化错误异常
     * @param exception
     * @return 异常处理统一应答
     */
    @ExceptionHandler(ClassCastException.class)
    public CommonResponse<Object> classCastExceptionException(ClassCastException exception) {

        log.error("类型转化异常:{}({}),异常工号:{},全局业务流水号：{}",
                exception.getMessage(), BaseContextHolder.getUserCode(),
                ExceptionUtil.getAllExceptionMsg(exception),BaseContextHolder.get(BaseContextConstants.GLOBAL_SERIAL_NUMBER));
        return CommonResponse.builder()
                .withCode(CommonErrorCode.CLASS_CAST_ERROR.getCode())// 依据设计文档定义B001 代表参数校验错误
                .withMessage(exception.getMessage())
                .build();
    }
    /**
     * 参数校验异常 NotNull NotEmpty 等触发该异常
     * @param exception  controller 或者 service的的
     * @return 统一应答
     */
    @ExceptionHandler(ValidationException.class)
    public CommonResponse<Object> handleValidationException(ValidationException exception) {
        //校验 除了 requestbody 注解方式的参数校验 对应的 bindingresult 为 BeanPropertyBindingResult

        log.error("参数校验异常:{}({}),异常工号:{},全局业务流水号：{}",
                exception.getMessage(), BaseContextHolder.getUserCode(),
                ExceptionUtil.getAllExceptionMsg(exception),
                BaseContextHolder.get(BaseContextConstants.GLOBAL_SERIAL_NUMBER));
        return CommonResponse.builder()
                .withCode(CommonErrorCode.SYSEM_PARAM_ERROR.getCode())// 依据设计文档定义B001 代表参数校验错误
                .withMessage(exception.getMessage())
                .build();
    }

    /**
     *  操作数据库发生异常将被该方法处理，在service层不需要经常数据库异常处理，请不要对mapper try catch
     *  数据库异常发生后由此方法统一处理
     * @param exception 数据库相关异常
     * @return 统一应答包含数据库的异常码 和 消息
     */
    @ExceptionHandler(SQLException.class)
    public CommonResponse<Object> databaseException(SQLException exception) {
        log.error("数据库层异常：{},异常工号:{},错误码:{}, 全局业务流水号：{}, 堆栈：{}",
                ExceptionUtil.getAllExceptionMsg(exception),BaseContextHolder.getUserCode(),exception.getErrorCode(),
                BaseContextHolder.get(BaseContextConstants.GLOBAL_SERIAL_NUMBER),
                exception);
        DataBaseException currentException=new DataBaseExceptionParser().parse(exception);
        return CommonResponse.builder()
                .withCode(String.valueOf(currentException.getCode()))
                .withMessage(exception.getMessage())
                .build();
    }

    /**
     *  捕获所有的未定义的异常
     * @param exception 控制层抛出的异常对象
     * @return 异常处理公共应答
     */
    @ExceptionHandler(Exception.class)
    public CommonResponse<Object> otherException(Exception exception) {
        log.error("[其他异常]: {},异常工号:{}, 全局业务流水号：{}, 堆栈：",
                exception.getMessage(),BaseContextHolder.getUserCode(), BaseContextHolder.get(BaseContextConstants.GLOBAL_SERIAL_NUMBER),exception);
        return CommonResponse.builder()
                .withCode(String.valueOf(CommonErrorCode.UNDEFINED.getCode()))
                .withMessage(exception.getMessage())
                .build();
    }

}
